package funtools.java;

import java.io.*;
import java.util.*;
import java.util.regex.Pattern;

import cn.hutool.core.util.StrUtil;
import lombok.Data;

/**
 * @author LiangMu
 * @version \$Id: ConvertProperties2Yaml.java, v 0.1, 2022年09月07日 20:49 LiangMu
 * Exp $$
 */
public class ConvertProperties2Yaml {
    private final static String              PROPERTIES_SUFFIX      = ".properties";
    private final static String              YAML_SUFFIX            = ".yaml";
    private final static String              TAB                    = "    ";
    private final static String              ARRAY_VALUE_START_MARK = "  - ";
    private final static String              ONE_SPACE              = " ";
    private final        Map<String, Config> configMap;
    private final        String              newPath;
    private final        File                propertiesFile;

    @Data
    private static class Config {
        private String       key;
        private String       value;
        private List<Config> children;

        public Config() {
            this.children = new ArrayList<Config>();
        }

        void addValue(String value) {
            if (this.value != null && this.value.length() > 0) {
                this.value += "," + value;
            } else {
                this.value = value;
            }
        }
    }

    public static void main(String[] args) throws FileNotFoundException {
        String path = "/Users/liangyuanliu/Desktop/projects/lm_demo/src/main/java/funtools/java/data/application.properties";
        convert(path);
    }

    private ConvertProperties2Yaml(String path) {
        if (StrUtil.isBlank(path)) {
            throw new RuntimeException("the path don't allow be blank");
        }
        if (!path.endsWith(PROPERTIES_SUFFIX)) {
            throw new RuntimeException("the path string format is not match");
        }
        this.newPath = path.substring(0, path.length() - PROPERTIES_SUFFIX.length()) + YAML_SUFFIX;
        this.configMap = new HashMap<>();
        File file = new File(path);
        if (!file.isFile()) {
            throw new RuntimeException("the path file is not a properties file");
        }
        this.propertiesFile = file;
    }

    private void convert() throws FileNotFoundException {
        Scanner scan = new Scanner(this.propertiesFile);
        try {
            while (scan.hasNextLine()) {
                String line = scan.nextLine();
                analyze(line);
            }
            this.configMap.values().forEach(this::sort);
            createFile();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            scan.close();
        }
    }

    private void createFile() throws IOException {
        File file = new File(this.newPath);
        if (file.exists()) {
            throw new RuntimeException("the file have exist, file path = ${instance.newPath}");
        }
        OutputStreamWriter writer = null;
        try {
            file.createNewFile();
            writer = new OutputStreamWriter(new FileOutputStream(file));
            Collection<Config> configList = this.configMap.values();
            for (Config config : configList) {
                this.write(config, writer);
            }
        } catch (Exception e) {
            file.delete();
            e.printStackTrace();
        } finally {
            writer.close();
        }
    }

    private void write(Config config, OutputStreamWriter writer) throws IOException {
        this.write(config, writer, 0);
        writer.write("\n");
    }

    private void write(Config config, OutputStreamWriter writer, Integer intervalLeft) throws IOException {
        String val = parseValue(config.value, intervalLeft);
        StringBuilder start = new StringBuilder();
        for (int i = 0; i < intervalLeft; i++) {
            start.append(TAB);
        }
        String line = start + ":" + val + "\n";
        writer.write(line);
        for (Config item : config.getChildren()) {
            this.write(item, writer, intervalLeft + 1);
        }
    }

    private String parseValue(String val, Integer intervalLeft) {
        if (StrUtil.isBlank(val)) return "";
        String[] valueArray = val.split(",");
        if (valueArray.length == 1) return ONE_SPACE + val;
        StringBuilder start = new StringBuilder();
        for (int i = 0; i < intervalLeft; i++) {
            start.append(TAB);
        }
        String begin = start + ARRAY_VALUE_START_MARK;
        StringBuilder result = new StringBuilder();
        for (String item : valueArray) {
            result.append(begin).append(item).append("\n");
        }
        return ONE_SPACE + "\n" + result;
    }

    private void sort(Config config) {
        List<Config> children = config.getChildren();
        children.sort(Comparator.comparing(Config::getKey));
        children.forEach(this::sort);
    }

    private void analyze(String text) {
        if (text.startsWith("#")) {
            return;
        }
        String[] map = text.split("=");
        if (map.length != 2) return;
        String configKey = map[0].trim();
        List<String> configKeys = Arrays.asList(configKey.split("\\."));
        final String key = configKeys.get(0);
        Config curConfig = Optional.ofNullable(this.configMap.get(key)).orElseGet(() -> {
            Config v = new Config();
            v.setKey(key);
            this.configMap.put(key, v);
            return v;
        });
        configKeys.remove(0);
        for (String item : configKeys) {
            Pattern pattern = Pattern.compile("\\S+\\[\\d+]");
            int lastIndex = item.lastIndexOf("[");
            boolean b = pattern.matcher(item).matches();
            String k = b ? item.substring(0, lastIndex) : item;
            Config temp = curConfig.children.stream().filter(item2 -> item2.getKey().equals(k)).findFirst().orElse(null);
            if (temp == null) {
                Config config = new Config();
                config.setKey(k);
                curConfig.getChildren().add(config);
                curConfig = config;
            } else {
                curConfig = temp;
            }
        }
        final String value = map[1].trim();
        curConfig.addValue(value);
    }

    private static void convert(String path) throws FileNotFoundException {
        new ConvertProperties2Yaml(path).convert();
    }
}
